package com.yupi.yurpc.proxy;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.yupi.yurpc.model.RpcRequest;
import com.yupi.yurpc.model.RpcResponse;
import com.yupi.yurpc.serializer.JdkSerializer;
import com.yupi.yurpc.serializer.Serializer;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @program: yu-rpc
 * @description: Java Lotus
 * @author: spring
 * @create: 2024-10-23
 */

/*
动态代理的作用是，根据要生成的对象的类型，自动生成一个代理对象。
常用的动态代理实现方式有 JDK 动态代理和基于字节码生成的动态代理（比如 CGLIB）
前者简单易用、无需引入额外的库，但缺点是只能对接口进行代理；
后者更灵活、可以对任何类进行代理，但性能略低于 JDK 动态代理。
此处我们使用 JDK 动态代理。
在 RPC 模块中编写动态代理类 ServiceProxy，
需要实现 InvocationHandler 接口的 invoke 方法。
代码如下（几乎就是把静态代理的代码搬运过来)
 */

/*
服务代理(JDK 动态代理)
 */
public class ServiceProxy implements InvocationHandler {

    /**
     * 调用代理
     *
     * @return 动态代理返回代理对象
     * @throws Throwable 抛出异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //指定序列化器
        Serializer serializer = new JdkSerializer();

        //构造请求
        RpcRequest rpcRequest = RpcRequest.builder()
                .serviceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .args(args)
                .build();

        //进行序列化产生的二进制数据进行相关的传输
        //发送了相关的信息和请求之后，我们再去进行反序列化进行对象的返回

        try {
            //序列化
            byte[] bodyBytes = serializer.serializer(rpcRequest);

            //发送请求
            // todo 注意，这里的地址被硬编码了 （需要使用注册中心和服务发现机制解决问题）

            try (HttpResponse httpResponse = HttpRequest.post("http://localhost:8080")
                    .body(bodyBytes)
                    .execute()) {
                byte[] result = httpResponse.bodyBytes();

                // 反序列化
                RpcResponse rpcResponse = serializer.deserializer(result, RpcResponse.class);
                return rpcResponse.getData();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }
}
/*
解释下上述代码，当用户调用某个接口的方法时，会改为调用 invoke 方法。
在 invoke 方法中，我们可以获取到要调用的方法信息、传入的参数列表等，
这不就是我们服务提供者需要的参数么？用这些参数来构造请求对象就可以完成调用了。
需要注意的是，上述代码中，请求的服务提供者地址被硬编码了，
需要使用注册中心和服务发现机制来解决。
 */